package com.foreveross.crawl.adapter.sub.impl20140402.v3.airchina;

import com.foreveross.crawl.adapter.CrawlAdapterFactory;
import com.foreveross.crawl.adapter.PlaneInfoEntityBuilder;
import com.foreveross.crawl.adapter.sub.impl20140402.v3.AirchinaAdapter;
import com.foreveross.crawl.common.util.DateUtil;
import com.foreveross.crawl.common.util.RegHtmlUtil;
import com.foreveross.crawl.domain.airfreight.AbstractPlaneInfoEntity;
import com.foreveross.crawl.domain.airfreight.CabinEntity;
import com.foreveross.crawl.domain.airfreight.TransitEntity;
import com.foreveross.crawl.domain.airfreight.doub.*;
import com.foreveross.crawl.exception.FlightInfoNotFoundException;
import com.foreveross.taskservice.common.bean.TaskModel;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.time.DateUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.NameValuePair;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.utils.URIUtils;
import org.apache.http.client.utils.URLEncodedUtils;
import org.apache.http.message.BasicNameValuePair;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;

import java.net.URI;
import java.net.URISyntaxException;
import java.text.ParseException;
import java.util.*;
import java.util.regex.Pattern;

/**
 * 国航国际适配器。
 *
 * @author luomingliang@foreveross.com
 * @version 1.0.0
 */
public class AirchinaInterTrip {
    protected Log logger = LogFactory.getLog(getClass());
    /**
     * ******************************** Params ***********************************************
     */
    private Long exe_time = 15000L; // 国航屏蔽IP,一定要隔一段时间抓取一次。
    private final Boolean IS_TAX = true; // 是否获取税费（只获取每条航线的最高和最低价格。）
    private final Boolean ONLY_LOWEST_TAX = true; // 是否只抓取最低价格税费。
    private final Boolean FETCH_ALL_TAX = true; // 是否抓取所有税费。 如果为False，则只保证起飞的税费，

    private static final String CHAR_SET = "UTF-8";
    private static final String REQUEST_HOST = "et.airchina.com.cn";
    private static final String REQUEST_PATH = "/InternetBooking/AirLowFareSearchExternal.do";
    private static final String dataUrl = "http://et.airchina.com.cn/InternetBooking/AirLowFareSearchExt.do";
    private Map<String, Double> taxMap = Maps.newHashMap();// 保存稅費信息：舱位号组合为Key。
    private Map<Double, Double> priceMap = Maps.newHashMap();// 保存稅費信息：价格为Key。

    private AirchinaAdapter adapter;
    private TaskModel taskQueue;

    /**
     * ******************************** Constructor ***********************************************
     */
    public AirchinaInterTrip(TaskModel taskQueue, AirchinaAdapter adapter) {
        this.taskQueue = taskQueue;
        this.adapter = adapter;
    }

    /**
     * ******************************** Fetch ***********************************************
     */

    private URI produceUri() throws URISyntaxException, ParseException {
        Calendar cal = Calendar.getInstance();
        Date gradDate = DateUtils.parseDate(taskQueue.getFlightDate(), "yyyy-MM-dd");
        cal.setTime(gradDate);
        int year = cal.get(Calendar.YEAR);
        int month = cal.get(Calendar.MONTH) + 1;
        int day = cal.get(Calendar.DATE);
        String depPort = taskQueue.getFromCity();
        String arrPort = getToCity();

        List<NameValuePair> qparams = new ArrayList<NameValuePair>();
        if (taskQueue.getIsReturn() == 1) {
            qparams.add(new BasicNameValuePair("tripType", "RT"));
            Date rgradDate = DateUtils.parseDate(taskQueue.getReturnGrabDate(), "yyyy-MM-dd");
            cal.setTime(rgradDate);
            int ryear = cal.get(Calendar.YEAR);
            int rmonth = cal.get(Calendar.MONTH) + 1;
            int rday = cal.get(Calendar.DATE);
            qparams.add(new BasicNameValuePair("inboundOption.departureDay", rday + ""));
            qparams.add(new BasicNameValuePair("inboundOption.departureMonth", rmonth + ""));
            qparams.add(new BasicNameValuePair("inboundOption.departureYear", ryear + ""));
        } else {
            qparams.add(new BasicNameValuePair("tripType", "OW"));
        }
        qparams.add(new BasicNameValuePair("searchType", "FARE"));
        qparams.add(new BasicNameValuePair("flexibleSearch", "false"));
        qparams.add(new BasicNameValuePair("directFlightsOnly", "false"));
        qparams.add(new BasicNameValuePair("fareOptions", "1.FAR.X"));
        qparams.add(new BasicNameValuePair("outboundOption.originLocationCode", depPort));
        qparams.add(new BasicNameValuePair("outboundOption.destinationLocationCode", arrPort));
        qparams.add(new BasicNameValuePair("outboundOption.departureDay", day + ""));
        qparams.add(new BasicNameValuePair("outboundOption.departureMonth", month + ""));
        qparams.add(new BasicNameValuePair("outboundOption.departureYear", year + ""));
        qparams.add(new BasicNameValuePair("outboundOption.departureTime", "NA"));
        qparams.add(new BasicNameValuePair("guestTypes[0].type", "ADT"));
        qparams.add(new BasicNameValuePair("guestTypes[0].amount", "1"));
        qparams.add(new BasicNameValuePair("guestTypes[1].type", "CNN"));
        qparams.add(new BasicNameValuePair("guestTypes[1].amount", "0"));
        qparams.add(new BasicNameValuePair("pos", "AIRCHINA_CN"));
        qparams.add(new BasicNameValuePair("lang", "zh_CN"));
        qparams.add(new BasicNameValuePair("guestTypes[2].type", "INF"));
        qparams.add(new BasicNameValuePair("guestTypes[2].amount", "0"));
        return URIUtils.createURI("http", REQUEST_HOST, -1, REQUEST_PATH, URLEncodedUtils.format(qparams, CHAR_SET), null);
    }

    /**
     * 抓取数据。
     *
     * @return
     * @throws Exception
     */
    public Object fetchData() throws Exception {
        logger.info("进入抓取数据方法 ...");
        String page = "";
        try {
            logger.info("设置Cookies信息，并获取数据。");
            adapter.excuteRequest(new HttpPost(produceUri())); // 设置Cookie信息。
            // 新版的Client自动设置Cookie信息。
            Thread.sleep(exe_time);
            page = adapter.excuteRequest(new HttpGet(dataUrl));// 获取页面
            validateFetch(page); // 验证
            adapter.appendPageContents(page);// 记录源网页
            return this.parseToVo(page);
        } finally {
            logger.info("数据抓取完成!");
            page = null;
        }
    }

    private boolean validateFetch(Object fetchObject) throws Exception {
        if (StringUtils.isEmpty(fetchObject.toString())) {
            throw new Exception("抓取到的页面为空！");
        }
        if (RegHtmlUtil.regMathcher(fetchObject.toString(), "WARNING_NO_RESULTS_FOR_SELECTED_DATES")) {
            throw new FlightInfoNotFoundException("所查找的日期没有对应的航班 ！");
        }
        return true;
    }

    /**
     * 从html字符串解析机票数据
     */
    public List<Object> parseToVo(Object fetchObject) throws Exception {
        logger.info("正在解析数据 ：" + taskQueue.getFromCity() + "-" + getToCity() + " begin : (paraseToVo) ):" + DateUtil.formatDay(new Date(), "yyyy-MM-dd hh:mm:ss"));

        List<AbstractPlaneInfoEntity> results = Lists.newArrayList();
        String fetchHtml = (String) fetchObject;

        List<String> shtml = this.getMlementContentReturn(fetchHtml, 0); // 获取去程所有航班
        Set<ReturnDoublePlaneInfoEntity> returnTrips = new HashSet<ReturnDoublePlaneInfoEntity>();
        Map<String, ReturnCabinEntity> lowHighMap = new HashMap<String, ReturnCabinEntity>();

        if (this.taskQueue.getIsReturn() == 1) { // 获取所有回程航班
            List<String> rshtml = this.getMlementContentReturn(fetchHtml, 1); // 获取去程所有航班
            logger.info("正在抓取稅費信息...");
            if (IS_TAX) setTaxPrice(shtml, rshtml); // 往返的时候获取最高和最低税费价格。
            logger.info("稅費抓取完成...");
            this.loadReturnPrice(returnTrips, lowHighMap, rshtml, null);// 也加载了returnTrips，但是不包括下一步产品类型
            if (returnTrips.size() == 0) {
                throw new FlightInfoNotFoundException("没有查到回程航班");
            }
        }
        String cid = null;
        List<String> cids = null;
        if (isHavReturnCabin(lowHighMap)) {
            cids = new Vector<String>(lowHighMap.keySet());
        }

        boolean first = true;
        for (Iterator<String> iterator = shtml.iterator(); iterator.hasNext(); ) {

            // 因为添加了每个返程最低价格，所以这里的返程对象不能复用，否则设置最低价格的时候会被覆盖。
            Set<ReturnDoublePlaneInfoEntity> returnPlaneInfos = newObjecctSet(returnTrips);

            if (isHavReturnCabin(lowHighMap)) {
                cid = cids.get(new Random().nextInt(cids.size()));
            }
            String html = iterator.next();
            AbstractPlaneInfoEntity planeInfo = null;

            if (first == true && this.taskQueue.getIsReturn() == 1) {
                planeInfo = this.getPlaneInfoEntity(html, lowHighMap, returnPlaneInfos, cid, first);// 第一次获取航班信息的时候要获取返程仓位高低价位下一页信息
                first = planeInfo == null;
            } else {
                planeInfo = this.getPlaneInfoEntity(html, lowHighMap, returnPlaneInfos, cid);
            }
            results.add(planeInfo);
       }

        logger.info("airchina explain " + taskQueue.getFromCity() + "-" + getToCity() + " end : (paraseToVo) ):" + DateUtil.formatDay(new Date(), "yyyy-MM-dd hh:mm:ss"));

        if (taskQueue.getIsReturn() == 0) PlaneInfoEntityBuilder.buildLimitPrice(results);
        else buildLimitPrice(results); // 设置最高和最低价格
        // 。
        Object ojb = results;
        return (List<Object>) ojb;
    }

    /**
     * ******************************** Fetch Tax ***********************************************
     */
    /**
     * 设置税费。
     *
     * @param goHtml
     * @param reHtml
     * @throws InterruptedException
     */
    private void setTaxPrice(List<String> goHtml, List<String> reHtml) throws InterruptedException {

        List<String> directCabins = Lists.newArrayList();//直飞的舱位。
        List<Map<String, Double>> golines = Lists.newArrayList();// 去程
        List<Map<String, Double>> relines = Lists.newArrayList();// 回程
        Map<String, Double> taxRelations = Maps.newHashMap();//需要计算价格的舱位组合 。
        try {

            assemblePriceNo(goHtml, golines, directCabins);// 组装去程价格和编号信息。
            assemblePriceNo(reHtml, relines, directCabins);// 组装回程价格和编号信息。

            //暂时只处理最低价格和最高价格的税费。
            for (Map<String, Double> goline : golines) { // 去程航班集合
                List<Map.Entry<String, Double>> goentrys = sortMapByValue(goline);

                String goBegin = goentrys.get(0).getKey(), goEnd = goentrys.get(goentrys.size() - 1).getKey();

                for (Map<String, Double> reline : relines) { // 去程航班價格集合。
                    List<Map.Entry<String, Double>> reentrys = sortMapByValue(reline);
                    String reBegin = reentrys.get(0).getKey(), reEnd = reentrys.get(reentrys.size() - 1).getKey();

                    Double subLowPrice = goentrys.get(0).getValue() + reentrys.get(0).getValue();
                    Double subHighPrice = goentrys.get(goentrys.size() - 1).getValue() + reentrys.get(reentrys.size() - 1).getValue();

                    //检查最低价格税费。
                    checkPriceHasTax(goBegin, reBegin, subLowPrice, directCabins);

                    //检查最高价格税费。
                    if (!ONLY_LOWEST_TAX)
                        checkPriceHasTax(goEnd, reEnd, subHighPrice, directCabins);
                }
            }

        } catch (Exception e) {
            logger.error(e);
        }

    }

    //Map排序。
    public List<Map.Entry<String, Double>> sortMapByValue(Map<String, Double> oriMap) {
        List<Map.Entry<String, Double>> entryList = new ArrayList<Map.Entry<String, Double>>(oriMap.entrySet());
        Collections.sort(entryList,
                new Comparator<Map.Entry<String, Double>>() {
                    public int compare(Map.Entry<String, Double> entry1, Map.Entry<String, Double> entry2) {
                        int value1 = 0, value2 = 0;
                        try {
                            value1 = entry1.getValue().intValue();
                            value2 = entry2.getValue().intValue();
                        } catch (NumberFormatException e) {
                            return 0;
                        }
                        return value1 - value2;
                    }
                });
        return entryList;
    }


    private void assemblePriceNo(List<String> htmls, List<Map<String, Double>> lines, List<String> directCabins) {
        Map<String, Double> line = null;
        String tmpHtml;
        Document doc;
        Elements eles;
        String priceno;
        String price;

        for (String str : htmls) {
            line = Maps.newHashMap(); // 用TreeMap排序 。
            tmpHtml = decodeUnicode(str);
            doc = Jsoup.parse("<table>" + tmpHtml + "</table>");

            //中转的次数。
            Elements trs = doc.select("html>body>table>tbody>tr");
            eles = doc.select("td.colCost table");
            for (Element ele : eles) {
                priceno = ele.select("div.colRadio > input").first().attr("value"); // 价格编号。
                price = ele.select("div.colPrice").first().text().replace(",", ""); // 价格 。
                line.put(priceno, Double.parseDouble(price)); // 把每個航班的價格和價格編號放到Map。
                if (trs.size() == 1) directCabins.add(priceno);//直飞的舱位号。
            }
            lines.add(line);
        }
    }

    //抓取税费。
    private void checkPriceHasTax(String goKey, String reKey, Double price, List<String> directCabins) {
        String key = goKey.concat("-").concat(reKey);
        Double tax = null;

        //非直飞的航班，按价格相同取税费。
        if (!FETCH_ALL_TAX && !directCabins.contains(goKey)) {
            tax = priceMap.get(price);
        }

        if (tax == null) { // 如果这个价格没有获取过税费则获取税费。
            tax = fetchPrice(goKey, reKey);
        }
        priceMap.put(price, tax);
        taxMap.put(key, tax);
        logger.info(String.format("税费信息。[%s]：%s", key, tax));
    }

    /**
     * 获取税费。
     *
     * @param gono 去程价格编号
     * @param reno 回程价格编号
     * @return
     */
    private int count = 0;

    private Double fetchPrice(String gono, String reno) {
        count++;
        System.out.println("第" + count + "次税费抓取。");
        String taxUrl = "http://et.airchina.com.cn/InternetBooking/AirSelectOWCFlight.do";
        Double taxFee = null;
        Map<String, String> params = Maps.newHashMap();
        params.put("alignment", "horizontal");
        params.put("combinabilityReloadRequired", "true");
        params.put("context", "airSelection");
        params.put("flowStep", "AIR_COMBINABLE_FARE_FAMILIES_SEARCH_RESULTS");
        params.put("isFareFamilySearchResult", "true");
        params.put("selectedFlightIds", String.format("0,%s,1,%s", gono, reno));
        params.put("selectedItineraries", String.format("0,%s", gono));
        params.put("selectedItineraries", String.format("1,%s", reno));
        try {
            Thread.sleep(exe_time);
            String page = adapter.excuteRequest(adapter.getBasePost(taxUrl, params));
            page = RegHtmlUtil.regStr(page, "bottomBot:\\s*?'(.*?)'");
            page = decodeUnicode(page);
            Document doc = Jsoup.parse("<html>" + page + "</html>");
            Elements eles = doc.select("table.detailedPrice td.price > div");

            String tax1 = eles.get(1).text().replace(",", ""); // 稅費
            String tax2 = eles.get(2).text().replace(",", ""); // 稅費
            taxFee = Double.parseDouble(tax1) + Double.parseDouble(tax2);
        } catch (Exception e) {
            logger.error(String.format("获取[%s-%s]最低价格税费信息失败！", gono, reno));
        }
        return taxFee;
    }

    /**
     * 克隆集合。
     *
     * @param inobjs
     * @return
     * @throws Exception
     */
    private <T> Set<T> newObjecctSet(Set<T> inobjs) throws Exception {
        // 因为添加了每个返程最低价格，所以这里的返回对象不能复用，需要创建新对象，否则设置最低价格的时候混乱。
        Set<T> outobjs = Sets.newHashSet();
        for (T obj : inobjs) {
            outobjs.add((T) BeanUtils.cloneBean(obj));
        }
        return outobjs;
    }

    /**
     * 是否有返程仓位
     *
     * @param lowHighMap
     * @return
     */
    private boolean isHavReturnCabin(Map<String, ReturnCabinEntity> lowHighMap) {
        return lowHighMap != null && lowHighMap.size() != 0;
    }

    /**
     * 获取返程最低价对象
     *
     * @param returnTrips
     * @param lowHighMap  最低价跟最高价回程仓位的集合
     * @param cookie
     * @return
     * @throws com.foreveross.crawl.exception.FlightInfoNotFoundException
     */
    public Double[] loadReturnPrice(Set<ReturnDoublePlaneInfoEntity> returnTrips, Map<String, ReturnCabinEntity> lowHighMap, List<String> shtml, String cookie) throws FlightInfoNotFoundException {
        if (shtml != null) {
            Integer rowi = -1;
            Map<Double, Integer> priceRow = new HashMap<Double, Integer>();
            for (Iterator<String> iterator = shtml.iterator(); iterator.hasNext(); ) {
                rowi++;
                String html = iterator.next();
                String tmpHtml = decodeUnicode(html);
                Document j = Jsoup.parse("<table>" + tmpHtml + "</table>");
                Elements tmp = j.getElementsByClass("colCost");

                List<String> flightNoList = RegHtmlUtil.retrieveLinks(html, ";return false;\\\\\"\\\\u003e([^\\d][\\w]+?)\\\\u003c", 1);// OK
                if (flightNoList == null) {
                    continue;
                }
                if (this.taskQueue.getIsReturn() == 1 && flightNoList.size() > 1) {// 中转不查返程
                    // continue;
                }

                Elements priceEle = j.getElementsByClass("colPrice");
                if (priceEle.size() == 0) {
                    continue;
                }
                loadgetPriceRow(priceEle, priceRow, rowi);
                Set<ReturnCabinEntity> cabins = this.getReturnCabins(cookie, tmp, lowHighMap);

                ReturnDoublePlaneInfoEntity returnTrip = getReturnTrip(html, j);
                returnTrip.setReturnCabins(cabins);

                returnTrips.add(returnTrip);
            }
        }
        return null;
    }

    /**
     * 解析返程航班。
     *
     * @param html
     * @param j
     * @return
     */
    private ReturnDoublePlaneInfoEntity getReturnTrip(String html, Document j) {

        // /航班编号List
        List<String> flightNoList = RegHtmlUtil.retrieveLinks(html, ";return false;\\\\\"\\\\u003e([^\\d][\\w]+?)\\\\u003c", 1);// OK
        if (flightNoList == null) {
            return null;
        }
        if (this.taskQueue.getIsReturn() == 1 && flightNoList.size() > 1) {// 中转不查返程
            // return null;
        }
        String flightNo = RegHtmlUtil.regStr(html, ";return false;\\\\\"\\\\u003e([\\w]+?)\\\\u003c", 1);// OK
        // /起飞时间list
        Elements depTimesList = j.getElementsByClass("colDepart");
        if (depTimesList.size() == 0) {
            return null;
        }

        // 到达时间List
        Elements arrTimesList = j.getElementsByClass("colArrive");
        if (arrTimesList.size() == 0) {
            return null;
        }

        List<ReturnTransitEntity> returnTransits = getReturnTransit(html, j, flightNoList, depTimesList, arrTimesList);

        // 航班类型型号LIST
        List<String> flightTypeNosList = RegHtmlUtil.retrieveLinks(html, "do\\?equipmentType=([\\w]{2,6})\\\\\'", 1); // /OK
        if (flightTypeNosList == null) {
            return null;
        }
        Elements airportInfo = j.getElementsByClass("simpleToolTip");
        List<String> carrierNames = new ArrayList<String>();
        List<String> fromAirs = new ArrayList<String>();
        List<String> toAirs = new ArrayList<String>();
        loadTransit(airportInfo, carrierNames, fromAirs, toAirs);
        ReturnDoublePlaneInfoEntity returnTrip = PlaneInfoEntityBuilder.getReturnTrip(taskQueue, "CA", carrierNames.get(0), carrierNames.get(0), depTimesList.get(0).text(), arrTimesList.get(0).text(), flightNo, null, null, null, null);
        returnTrip.setStartTime(returnTransits.get(0).getStartTime());
        returnTrip.setEndTime(returnTransits.get(returnTransits.size() - 1).getEndTime());
        if (returnTransits.size() > 1) // 设置回程中转
            returnTrip.getReturnTransits().addAll(returnTransits);
        return returnTrip;
    }

    private void loadgetPriceRow(Elements priceEle, Map priceRow, Integer rowi) {
        for (Element p : priceEle) {
            Double price = Double.valueOf(p.text().replace(",", ""));
            if (rowi >= 0) {
                priceRow.put(price, rowi);
            }
        }
    }

    private AbstractPlaneInfoEntity getPlaneInfoEntity(String html, Map<String, ReturnCabinEntity> lowHighMap, Set<ReturnDoublePlaneInfoEntity> returnTrips, String cid) throws Exception {
        return this.getPlaneInfoEntity(html, lowHighMap, returnTrips, cid, false);
    }

    /**
     * 获取航班信息 如果是查往返 ，排除中转; lowHighMap!=null的时候要获取返程仓位高低价位下一页信息
     *
     * @param html
     * @param lowHighMap
     * @param returnTrips
     * @param cid         随机的第一个仓位id
     * @return
     * @throws Exception
     */
    private AbstractPlaneInfoEntity getPlaneInfoEntity(String html, Map<String, ReturnCabinEntity> lowHighMap, Set<ReturnDoublePlaneInfoEntity> returnTrips, String cid, boolean first) throws Exception {
        AbstractPlaneInfoEntity result = null;
        String tmpHtml = decodeUnicode(html);
        Document j = Jsoup.parse("<table>" + tmpHtml + "</table>");
        Elements tmp = j.getElementsByClass("colCost");

        // /航班编号List
        List<String> flightNoList = RegHtmlUtil.retrieveLinks(html, ";return false;\\\\\"\\\\u003e([^\\d][\\w]+?)\\\\u003c", 1);// OK
        if (flightNoList == null) {
            return null;
        }

        // /起飞时间list
        Elements depTimesList = j.getElementsByClass("colDepart");
        if (depTimesList.size() == 0) {
            return null;
        }

        // 到达时间List
        Elements arrTimesList = j.getElementsByClass("colArrive");
        if (arrTimesList.size() == 0) {
            return null;
        }

        List<TransitEntity> transit = getTransit(html, j, flightNoList, depTimesList, arrTimesList);
        if (transit == null) {// 如果是查往返 ，排除中转
            return null;
        }

        String flightNo = RegHtmlUtil.regStr(html, ";return false;\\\\\"\\\\u003e([\\w]+?)\\\\u003c", 1);// OK
        logger.info(flightNo);
        if (StringUtils.isEmpty(flightNo)) {
            return null;
        }

        // 航班类型型号
        String flightTypeNos = RegHtmlUtil.regStr(html, "do\\?equipmentType=([\\w]{2,6})\\\\\'", 1); // /OK
        if (StringUtils.isEmpty(flightTypeNos)) {
            return null;
        }

        if (transit.size() > 1) {// 中转
            result = PlaneInfoEntityBuilder.buildPlaneInfo(taskQueue, "CA", "国航", "中国国际航空公司", null, null, flightNo, null, null, null, flightTypeNos);
            result.setStartTime(transit.get(0).getStartTime());
            result.setEndTime(transit.get(transit.size() - 1).getEndTime());

            ArrayList<TransitEntity> transitSet = new ArrayList<TransitEntity>(transit);
            PlaneInfoEntityBuilder.setTransits(result, transitSet);
            // System.out.println(flightNo);
        } else {// 非中转
            result = PlaneInfoEntityBuilder.buildPlaneInfo(taskQueue, "CA", "国航", "中国国际航空公司", depTimesList.get(0).text().trim(), arrTimesList.get(0).text().trim(), flightNo, null, null, null, flightTypeNos);
        }
        DoublePlaneInfoEntity doublePlaneInfoEntity = null;
        if (taskQueue.getIsReturn() == 1) {
            doublePlaneInfoEntity = PlaneInfoEntityBuilder.getDoubleEntity(result);
            doublePlaneInfoEntity.setReturnPlaneInfos(returnTrips);
        }
        Set<CabinEntity> cabins = getCabins(tmp, lowHighMap, doublePlaneInfoEntity, cid, first);
        PlaneInfoEntityBuilder.setCabin(result, cabins);
        return result;
    }

    /**
     * 不中转的航班返回list的大小为1 返程不查中转，返回list为1
     *
     * @param html
     * @param j
     * @param arrTimesList
     * @param depTimesList
     * @param flightNoList
     * @return
     */
    private List<TransitEntity> getTransit(String html, Document j, List<String> flightNoList, Elements depTimesList, Elements arrTimesList) {
        List<TransitEntity> transit;
        transit = new ArrayList<TransitEntity>();

        // 航班类型型号LIST
        List<String> flightTypeNosList = RegHtmlUtil.retrieveLinks(html, "do\\?equipmentType=([\\w]{2,6})\\\\\'", 1); // /OK
        if (flightTypeNosList == null) {
            return null;
        }

        // 出发地三字码List
        List<String> fromCityCodeList = RegHtmlUtil.retrieveLinks(html, "&origin=([\\w]+?)&destination", 1);
        if (fromCityCodeList == null) {
            return null;
        }
        // 到达地三字码LIST
        List<String> toCityCodeList = RegHtmlUtil.retrieveLinks(html, "destination=([\\w]+?)&departureDay", 1);
        if (toCityCodeList == null) {
            return null;
        }

        // 机场和承运人信息
        Elements airportInfo = j.getElementsByClass("simpleToolTip");
        List<String> carrierNames = new ArrayList<String>();
        List<String> fromAirs = new ArrayList<String>();
        List<String> toAirs = new ArrayList<String>();
        loadTransit(airportInfo, carrierNames, fromAirs, toAirs);

        for (int k = 0; k < depTimesList.size(); k++) {
            TransitEntity entity = new TransitEntity();
            entity.setCarrierFullName(carrierNames.get(k));
            entity.setCarrierName(carrierNames.get(k));
            // entity.setLowerPrice(prices[0]);
            // entity.setHightPrice(prices[prices.length - 1]);
            entity.setFromAirPortName(fromAirs.get(k));
            entity.setToAirPortName(toAirs.get(k));
            entity.setFlightType(flightTypeNosList.get(k));
            entity.setFlightNo(flightNoList.get(k));
            entity.setFromAirPort(fromCityCodeList.get(k));
            entity.setToAirPort(toCityCodeList.get(k));
            entity.setStartTime(CrawlAdapterFactory.getFlightTime(taskQueue.getFlightDate(), depTimesList.get(k).text()));
            entity.setEndTime(CrawlAdapterFactory.getFlightTime(taskQueue.getFlightDate(), arrTimesList.get(k).text()));
            transit.add(entity);
        }
        return transit;
    }

    /**
     * 不中转的航班返回list的大小为1 返程不查中转，返回list为1
     *
     * @param html
     * @param j
     * @param arrTimesList
     * @param depTimesList
     * @param flightNoList
     * @return
     */
    private List<ReturnTransitEntity> getReturnTransit(String html, Document j, List<String> flightNoList, Elements depTimesList, Elements arrTimesList) {
        List<ReturnTransitEntity> retransits = Lists.newArrayList();

        // 航班类型型号LIST
        List<String> flightTypeNosList = RegHtmlUtil.retrieveLinks(html, "do\\?equipmentType=([\\w]{2,6})\\\\\'", 1); // /OK
        if (flightTypeNosList == null) {
            return null;
        }

        // 出发地三字码List
        List<String> fromCityCodeList = RegHtmlUtil.retrieveLinks(html, "&origin=([\\w]+?)&destination", 1);
        if (fromCityCodeList == null) {
            return null;
        }
        // 到达地三字码LIST
        List<String> toCityCodeList = RegHtmlUtil.retrieveLinks(html, "destination=([\\w]+?)&departureDay", 1);
        if (toCityCodeList == null) {
            return null;
        }

        // 机场和承运人信息
        Elements airportInfo = j.getElementsByClass("simpleToolTip");
        List<String> carrierNames = new ArrayList<String>();
        List<String> fromAirs = new ArrayList<String>();
        List<String> toAirs = new ArrayList<String>();
        loadTransit(airportInfo, carrierNames, fromAirs, toAirs);

        for (int k = 0; k < depTimesList.size(); k++) {
            try {
                ReturnTransitEntity reentity = new ReturnTransitEntity();
                reentity.setCarrierFullName(carrierNames.get(k));
                reentity.setFromAirPortName(fromAirs.get(k));
                reentity.setToAirPortName(toAirs.get(k));
                reentity.setFlightType(flightTypeNosList.get(k));
                reentity.setFlightNo(flightNoList.get(k));
                reentity.setFromAirPort(fromCityCodeList.get(k));
                reentity.setToAirPort(toCityCodeList.get(k));
                reentity.setStartTime(CrawlAdapterFactory.getFlightTime(taskQueue.getReturnGrabDate(), depTimesList.get(k).text()));
                reentity.setEndTime(CrawlAdapterFactory.getFlightTime(taskQueue.getReturnGrabDate(), arrTimesList.get(k).text()));
                retransits.add(reentity);
            } catch (Exception e) {
                logger.error("回程航班组装出错：" + e.getMessage());
            }
        }
        return retransits;
    }

    /**
     * 加载机场和承运人信息
     *
     * @param airportInfo
     * @param carrierNames
     * @param fromAirs
     * @param toAirs
     */
    private void loadTransit(Elements airportInfo, List<String> carrierNames, List<String> fromAirs, List<String> toAirs) {
        String[] tmpari = null;
        for (int k = 0; k < airportInfo.size(); k++) {
            Element e = airportInfo.get(k);
            /*
             * 有承运公司 原型是： <div class="simpleToolTip"> <p>承运航空公司：深圳航空有限责任公司</p> </div> <div class="simpleToolTip"> <p> 广州白云机场 (CAN) - 成都双流机场 (CTU)</p> </div> <div class="simpleToolTip"> <p> 这个价格还<b>剩余7
			 * 个座位</b><br /></p> </div> <div class="simpleToolTip"> <p> 这个价格还<b>剩余4 个座位</b><br /></p> </div>
			 */
            if (e.text().indexOf("承运航空公司") != -1) {
                carrierNames.add(e.text());
                while (true) {
                    tmpari = airportInfo.get(++k).text().split(" - ");
                    if (tmpari.length > 1) {
                        fromAirs.add(tmpari[0]);
                        toAirs.add(tmpari[1]);
                        break;
                    }
                }

            } else {
                /*
                 * 无承运公司 原型是 <div class="simpleToolTip"> <p> 广州白云机场 (CAN) - 成都双流机场 (CTU)</p> </div> <div class="simpleToolTip"> <p> 这个价格还<b>剩余7 个座位</b><br /></p> </div>
				 */
                tmpari = airportInfo.get(k).text().split(" - ");
                if (tmpari.length > 1) {
                    carrierNames.add("中国国际航空股份有限公司");
                    fromAirs.add(tmpari[0]);
                    toAirs.add(tmpari[1]);
                }
            }
        }
    }

    /**
     * 获取返程仓位
     */
    private Set<ReturnCabinEntity> getReturnCabins(String cookie, Elements tmp, Map<String, ReturnCabinEntity> lowHighMap) {
        // 获取仓位
        Set<ReturnCabinEntity> cabins = new HashSet<ReturnCabinEntity>();
        for (int i = 0; i < tmp.size(); i++) {
            try {
                String price = RegHtmlUtil.regStr(tmp.get(i).toString(), "flightSelectGr_1_([\\d]+?)\">(.*?)</", 2);
                String id = RegHtmlUtil.regStr(tmp.get(i).toString(), "flightSelectGr_1_([\\d]+?)\">(.*?)</", 1);
                if (price != null) {
                    ReturnCabinEntity c = new ReturnCabinEntity();
                    String cabinType = null;
                    String productName = null;
                    price = price.replace(",", "");
                    if (tmp.size() == 5) {
                        cabinType = getCabinType(i);
                        productName = getProductName(i);
                    } else if (tmp.size() == 8) {
                        cabinType = getCabinTypeInte(i);
                        productName = getProductNameInte(i);
                    }

                    c = PlaneInfoEntityBuilder.buildCabinInfo(cabinType, null, productName, null, price, null, null, null, ReturnCabinEntity.class);
                    c.setTuigaiqian(id);//这里先借来保存舱位编号。

                    cabins.add(c);
                    lowHighMap.put(id, c); // 保存所有仓们信息。
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return cabins;
    }

    /**
     * 获取仓位
     */
    private Set<CabinEntity> getCabins(Elements tmp, Map<String, ReturnCabinEntity> lowHighMap, DoublePlaneInfoEntity doublePlaneInfoEntity, String cid, boolean first) {
        // 获取仓位
        Set<CabinEntity> cabins = new HashSet<CabinEntity>();

        for (int i = 0; i < tmp.size(); i++) {
            String cabinType = null;
            String productName = null;
            try {
                String price = RegHtmlUtil.regStr(tmp.get(i).toString(), "flightSelectGr_0_([\\d]+?)\">(.*?)</", 2);
                String id = RegHtmlUtil.regStr(tmp.get(i).toString(), "flightSelectGr_0_([\\d]+?)\">(.*?)</", 1);

                if (price != null) {
                    price = price.replace(",", "");
                    if (tmp.size() == 5) {
                        cabinType = getCabinType(i);
                        productName = getProductName(i);
                    } else if (tmp.size() == 8) {
                        cabinType = getCabinTypeInte(i);
                        productName = getProductNameInte(i);
                    }

                    CabinEntity c = new CabinEntity();
                    c = PlaneInfoEntityBuilder.buildCabinInfo(cabinType, null, productName, null, price, null, null, null, CabinEntity.class);
                    c.setTuigaiqian(id);//这里先借来保存舱位编号。
                    if (taskQueue.getIsInternational() == 1) {
                        c.setProductName(productName);
                    }

                    cabins.add(c);
                    if (doublePlaneInfoEntity != null && isHavReturnCabin(lowHighMap)) {
                        for (String key : lowHighMap.keySet()) {
                            ReturnCabinEntity returnCabin = lowHighMap.get(key);
                            Double allprice = c.getPrice() + returnCabin.getPrice();
//                            Double taxprice = taxMap.get(allprice); // 從Map中获取税费(以价格为Key)。
                            Double taxprice = taxMap.get(id.concat("-").concat(key)); // 從Map中获取税费（以去程舱位+回程舱位为Key）。
                            Double amount = AirchinaAdapterUtil.addDouble(taxprice, allprice);// 如果稅費為空不計算總價。

                            CabinRelationEntity relation = new CabinRelationEntity();
                            relation.setCabinId(c.getId());
                            relation.setReturnCabinId(returnCabin.getId());
                            relation.setTaxesPrice(taxprice); // 税费
                            relation.setFullPrice(allprice); // 裸价
                            relation.setTotalFullPrice(amount); // 总价
                            doublePlaneInfoEntity.getCabinRelations().add(relation);
                        }
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        return cabins;
    }

    /**
     * 正则匹配得到每一行的记录
     */
    private List<String> getMlementContentReturn(String sHtml, int isReturn) {
        int i = 0;
        if (isReturn == 1) {
            i = 1;
        }
        String html = null;
        html = RegHtmlUtil.regStr(sHtml, "tdGroupData\\[" + i + "\\]\\s*?=\\s*?\\{(.+?)\\};", 1, Pattern.CASE_INSENSITIVE | Pattern.COMMENTS | Pattern.DOTALL);
        return RegHtmlUtil.retrieveLinks(html, "\"([-]*[\\d]+)\":\\s*\"(.*?)\\\\n\\\\t\\\\t\\\\t\\\\t\"", 2, Pattern.DOTALL);// ok
    }

    /**
     * ******************************** Tools ***********************************************
     */

    /**
     * unicode 转换成 中文
     */
    private static String decodeUnicode(String theString) {
        char aChar;
        int len = theString.length();
        StringBuffer outBuffer = new StringBuffer(len);
        for (int x = 0; x < len; ) {
            aChar = theString.charAt(x++);
            if (aChar == '\\') {
                aChar = theString.charAt(x++);
                if (aChar == 'u') {
                    // Read the xxxx
                    int value = 0;
                    for (int i = 0; i < 4; i++) {
                        aChar = theString.charAt(x++);
                        switch (aChar) {
                            case '0':
                            case '1':
                            case '2':
                            case '3':
                            case '4':
                            case '5':
                            case '6':
                            case '7':
                            case '8':
                            case '9':
                                value = (value << 4) + aChar - '0';
                                break;
                            case 'a':
                            case 'b':
                            case 'c':
                            case 'd':
                            case 'e':
                            case 'f':
                                value = (value << 4) + 10 + aChar - 'a';
                                break;
                            case 'A':
                            case 'B':
                            case 'C':
                            case 'D':
                            case 'E':
                            case 'F':
                                value = (value << 4) + 10 + aChar - 'A';
                                break;
                            default:
                                throw new IllegalArgumentException("Malformed   \\uxxxx   encoding.");
                        }
                    }
                    outBuffer.append((char) value);
                } else {
                    if (aChar == 't') aChar = '\t';
                    else if (aChar == 'r') aChar = '\r';
                    else if (aChar == 'n') aChar = '\n';
                    else if (aChar == 'f') aChar = '\f';
                    outBuffer.append(aChar);
                }
            } else outBuffer.append(aChar);
        }
        return outBuffer.toString();
    }

    /**
     * 国内仓位
     *
     * @param i
     * @return
     */
    private String getCabinType(int i) {

        switch (i) {
            case 0:
                return "头等";
            case 1:
                return "公务";
            case 2:
                return "全价";
            case 3:
                return "折扣";
            case 4:
                return "特价";
            default:
                return null;
        }
    }

    private String getProductName(int i) {
        switch (i) {
            case 0:
                return "头等";
            case 1:
                return "公务";
            case 2:
                return "全价";
            case 3:
                return "折扣";
            case 4:
                return "特价";
            default:
                return null;
        }
    }

    /**
     * 获取国际仓位
     *
     * @param i
     * @return
     */
    private String getProductNameInte(int i) {
        switch (i) {
            case 0:
                return "头等";
            case 1:
                return "头等折扣";
            case 2:
                return "公务";
            case 3:
                return "公务折扣";
            case 4:
                return "高端全价";
            case 5:
                return "商旅知音";
            case 6:
                return "折扣经济";
            case 7:
                return "超值特价";
            default:
                return null;
        }
    }

    private String getCabinTypeInte(int i) {
        switch (i) {
            case 0:
            case 1:
                return "头等舱";
            case 2:
            case 3:
                return "公务舱";
            case 4:
            case 5:
            case 6:
            case 7:
                return "经济舱";
            default:
                return null;
        }
    }

    private String getToCity() {
        String toCity = taskQueue.getToCity();
        if (StringUtils.isNotBlank(toCity) && "LON".equalsIgnoreCase(toCity)) {
            toCity = "LHR";
        }
        return toCity;
    }

    public void buildLimitPrice(List<? extends AbstractPlaneInfoEntity> planeInfos) {
        if (planeInfos == null || planeInfos.size() == 0) {
            return;
        }
        //AbstractPlaneInfoEntity first = planeInfos.get(0);

        for (AbstractPlaneInfoEntity absPlaneInfo : planeInfos) {
            if (absPlaneInfo instanceof DoublePlaneInfoEntity) { // 往返去程
                DoublePlaneInfoEntity planeInfo = (DoublePlaneInfoEntity) absPlaneInfo;
                List<CabinRelationEntity> tempCabin = Lists.newArrayList();
                tempCabin.addAll(planeInfo.getCabinRelations());
                AirchinaAdapterUtil.orderByRelation(tempCabin);
                CabinRelationEntity low = tempCabin.get(0);
                CabinRelationEntity high = tempCabin.get(tempCabin.size() - 1);

                absPlaneInfo.setTotalLowestPrice(low.getFullPrice());
                absPlaneInfo.setTotalLowestTaxesPrice(low.getTaxesPrice());
                absPlaneInfo.setSumLowestPrice(low.getTotalFullPrice());
                absPlaneInfo.setTotalHighestPrice(high.getFullPrice());
                absPlaneInfo.setTotalHighestTaxesPrice(high.getTaxesPrice());
                absPlaneInfo.setSumHighestPrice(high.getTotalFullPrice());

                // 获取去程的舱位最低价格
                ArrayList<CabinEntity> goCabins = new ArrayList<CabinEntity>();
                goCabins.addAll(planeInfo.getCabins());
                PlaneInfoEntityBuilder.orderBySinglePrice(goCabins);
                CabinEntity golow = goCabins.get(0);
                CabinEntity gohigh = goCabins.get(goCabins.size() - 1);
                //舱位无价格，合计价，用relation的记录
                if (!tempCabin.isEmpty() && (!PlaneInfoEntityBuilder.isNullOrZero(tempCabin.get(0).getFullPrice()) || !PlaneInfoEntityBuilder.isNullOrZero(tempCabin.get(0).getTotalFullPrice()))) {
                    // 设置回程最低价格。
                    for (ReturnDoublePlaneInfoEntity returnInfo : planeInfo.getReturnPlaneInfos()) {

                        // 获取去程的最低价格舱位
                        List<ReturnCabinEntity> reCabins = Lists.newArrayList();
                        reCabins.addAll(returnInfo.getReturnCabins());
                        PlaneInfoEntityBuilder.orderByReturnCabinPrice(reCabins);
                        ReturnCabinEntity returnlow = reCabins.get(0);
                        ReturnCabinEntity returnhigh = reCabins.get(reCabins.size() - 1);

                        String key = golow.getTuigaiqian().concat("-").concat(returnlow.getTuigaiqian());
                        Double lowTax = taxMap.get(key);
                        System.out.println("最低价格组合 ："+key +">"+lowTax);
                        Double highTax = taxMap.get(gohigh.getTuigaiqian().concat("-").concat(returnhigh.getTuigaiqian()));

                        Double lowPrice = addDouble(golow.getPrice(), returnlow.getPrice());
                        Double highPrice = addDouble(gohigh.getPrice(), returnhigh.getPrice());

                        // 回程的最低打包价=当前回程航班舱位最低价格加上去程航班仓位最低价
                        returnInfo.setTotalLowestPrice(lowPrice);
                        returnInfo.setTotalHighestPrice(highPrice);
                        returnInfo.setTotalLowestTaxesPrice(lowTax);
                        returnInfo.setTotalHighestTaxesPrice(highTax);
                        returnInfo.setSumLowestPrice(addDouble(lowPrice, lowTax));
                        returnInfo.setSumHighestPrice(addDouble(highPrice, highTax));
                    }
                }
            }
        }
    }

    private static Double addDouble(Double d1, Double d2) {
        if (d1 == null && d2 == null) {
            return null;
        }
        return (d1 == null ? 0 : d1) + (d2 == null ? 0 : d2);
    }
}
